/*jslint regexp: true */
/*globals extend, Class */

/**
 * Класс для хранения и управления параметрами.
 *
 * @class
 * @name Options
 * @extends Class
 * @see Options#getItem
 * @see Options#setItem
 * @see Options#hasItem
 * @see Options#getItems
 * @see Options#setItems
 * @see Options#addItems
 * @see Options#removeItem
 * @see Options#forEach
 * @see Options#clear
 * @see Options#extend
 */
var Options = Class.$extend({

    /**
     * Параметры объекта.
     *
     * @private
     * @field
     * @name Options#options
     * @type {Object|null}
     */
    options : null,

    /**
     * Конструктор.
     *
     * @public
     * @function
     * @constructs
     * @name Options#$init
     * @param {Object} [items] Параметры.
     * @return {void}
     */
    $init : function (items) {
        'use strict';
        this.setItems(items);
    },

    /**
     * Получить параметры в виде простого объекта.
     *
     * @public
     * @function
     * @name Options#getItems
     * @return {Object}
     */
    getItems : function () {
        'use strict';
        var options = {},
            self = this;
        // Клонируем объект текущих параметров.
        if (this.options !== null) {
            Object.keys(this.options).forEach(function (key) {
                options[key] = self.options[key];
            });
        }
        return options;
    },

    /**
     * Установить параметры простым объектом.
     *
     * @public
     * @function
     * @name Options#setItems
     * @param {Object} items Объект параметров.
     * @return {void}
     */
    setItems : function (items) {
        'use strict';
        this.options = {};
        this.addItems(items);
    },

    /**
     * Добавить параметры простым объектом.
     *
     * @public
     * @function
     * @name Options#addItems
     * @param {Object|Options} items Набор новых значений.
     * @return {void}
     */
    addItems : function (items) {
        'use strict';
        var self = this;
        // Устанавливаем новые параметры объекта.
        if (typeof items === 'object' && items !== null) {
            Object.keys(items).forEach(function (name) {
                self.setItem(name, items[name]);
            });
        }
    },

    /**
     * Получить значение параметра.
     *
     * @public
     * @function
     * @name Options#getItem
     * @param {String} name Название параметра.
     * @param {*} [defaults] Значение по умолчанию, если параметр не был найден.
     * @return {*}
     */
    getItem : function (name, defaults) {
        'use strict';
        name = String(name || '');
        // Проверяем, что название параметра валидно.
        if (!/^[a-z_]\w*$/i.test(name)) {
            throw new TypeError('The option name(' + JSON.stringify(name) + ') is not valid.');
        }
        // Безопастное использование параметров.
        if (this.options === null) {
            this.options = {};
        }
        // Если нашлось значение в списке, то отдаем его.
        if (['string', 'number', 'boolean', 'object'].indexOf(typeof this.options[name]) !== -1) {
            return this.options[name];
        }
        return defaults;
    },

    /**
     * Установить значение параметра.
     *
     * @public
     * @function
     * @name Options#setItem
     * @param {String} name Название параметра.
     * @param {String|Number|Boolean} value Значение параметра.
     * @return {void}
     */
    setItem : function (name, value) {
        'use strict';
        name = String(name || '');
        // Проверяем, что название параметра валидно.
        if (!/^[a-z_]\w*$/i.test(name)) {
            throw new TypeError('The option name(' + JSON.stringify(name) + ') is not valid.');
        }
        // Безопастное использование параметров.
        if (this.options === null) {
            this.options = {};
        }
        // Если значение нужного типа, то добавляем его в список.
        if (['string', 'number', 'boolean', 'object'].indexOf(typeof value) !== -1) {
            this.options[name] = value;
        }
    },

    /**
     * Проверить, существуеит ли параметр.
     *
     * @public
     * @function
     * @name Options#hasItem
     * @param {String} name Название параметра.
     * @return {Boolean}
     */
    hasItem : function (name) {
        'use strict';
        name = String(name || '');
        // Проверяем, что название параметра валидно.
        if (!/^[a-z_]\w*$/i.test(name)) {
            throw new TypeError('The option name(' + JSON.stringify(name) + ') is not valid.');
        }
        // Безопастное использование параметров.
        if (this.options === null) {
            this.options = {};
        }
        return ['string', 'number', 'boolean', 'object'].indexOf(typeof this.options[name]) !== -1;
    },

    /**
     * Удалить параметр.
     *
     * @public
     * @function
     * @name Options#removeItem
     * @param {String} name Название параметра.
     * @return {void}
     */
    removeItem : function (name) {
        'use strict';
        if (this.hasItem(name)) {
            delete this.options[name];
        }
    },

    /**
     * Перебрать все параметры объекта.
     *
     * @public
     * @function
     * @name Options#forEach
     * @param {Function} callback Обработчик каждого параметра.
     * @return {void}
     */
    forEach : function (callback) {
        'use strict';
        var self = this;
        if (typeof callback === 'function') {
            Object.keys(this.options).forEach(function (name) {
                callback.call(self, name, self.getItem(name));
            });
        }
    },

    /**
     * Очистить все параметры.
     *
     * @public
     * @function
     * @name Options#clear
     * @return {void}
     */
    clear : function () {
        'use strict';
        this.options = {};
    },

    /**
     * Расширение другим объектом параметров.
     *
     * @public
     * @function
     * @name Options#extend
     * @param {Options} options Объект параметров.
     */
    extend : function (options) {
        'use strict';
        var self = this;
        if (options instanceof Options) {
            options.forEach(function (name, value) {
                self.setItem(name, value);
            });
        }
    }

});